import torch
import torch.utils.data
import numpy as np
import cv2
from torchvision import transforms
import pandas as pd
from skimage import io, transform
from src import const
import random
import skimage
import imgaug as ia
from imgaug import augmenters as iaa


class RandomFlip(object):

    def __call__(self, image):
        h, w = image.shape[:2]
        if np.random.rand() > 0.5:
            image = np.fliplr(image)

        return image


class CenterCrop(object):

    def __init__(self, output_size):
        assert isinstance(output_size, (int, tuple))
        if isinstance(output_size, int):
            self.output_size = (output_size, output_size)
        else:
            assert len(output_size) == 2
            self.output_size = output_size

    def __call__(self, image):
        h, w = image.shape[:2]
        new_h, new_w = self.output_size

        top = int((h - new_h) / 2)
        left = int((w - new_w) / 2)

        image = image[top: top + new_h,
                      left: left + new_w]

        return image


class RandomCrop(object):
    """Crop randomly the image in a sample.

    Args:
        output_size (tuple or int): Desired output size. If int, square crop
            is made.
    """

    def __init__(self, output_size):
        assert isinstance(output_size, (int, tuple))
        if isinstance(output_size, int):
            self.output_size = (output_size, output_size)
        else:
            assert len(output_size) == 2
            self.output_size = output_size

    def __call__(self, image):
        h, w = image.shape[:2]
        new_h, new_w = self.output_size

        top = random.randint(0, h - new_h)
        left = random.randint(0, w - new_w)

        image = image[top: top + new_h,
                      left: left + new_w]

        return image


class RandomRescale(object):

    def __init__(self, output_size_range):
        '''
        output_size_range指将短边缩放到的范围
        '''
        assert isinstance(output_size_range, tuple)
        self.lower_size = int(output_size_range[0])
        self.upper_size = int(output_size_range[1])

    def gen_output_size(self):
        return random.randint(self.lower_size, self.upper_size)

    def __call__(self, image):
        h, w = image.shape[:2]
        output_size = self.gen_output_size()
        if h > w:
            new_h, new_w = output_size * h / w, output_size
        else:
            new_h, new_w = output_size, output_size * w / h

        new_h, new_w = int(new_h), int(new_w)

        img = transform.resize(image, (new_h, new_w), mode='constant')

        return img


class Rescale(object):
    """Rescale the image in a sample to a given size.

    Args:
        output_size (tuple or int): Desired output size. If tuple, output is
            matched to output_size. If int, smaller of image edges is matched
            to output_size keeping aspect ratio the same.
    """

    def __init__(self, output_size):
        assert isinstance(output_size, (int, tuple))
        self.output_size = output_size

    def __call__(self, image):
        h, w = image.shape[:2]
        if isinstance(self.output_size, int):
            if h > w:
                new_h, new_w = self.output_size * h / w, self.output_size
            else:
                new_h, new_w = self.output_size, self.output_size * w / h
        else:
            new_h, new_w = self.output_size

        new_h, new_w = int(new_h), int(new_w)

        img = transform.resize(image, (new_h, new_w), mode='constant')

        # h and w are swapped for landmarks because for images,
        # x and y axes are axis 1 and 0 respectively

        return img


class RandomGamma(object):

    def __init__(self, gamma_range):
        self.lower_gamma = gamma_range[0]
        self.upper_gamma = gamma_range[1]

    def __call__(self, image):
        return skimage.exposure.adjust_gamma(
            image,
            random.uniform(self.lower_gamma, self.upper_gamma)
        )


class CelebaAttDataset(torch.utils.data.Dataset):

    def __init__(self, df, mode):
        '''
        mode:
            RESCALE227：直接缩放到227x227
            RESCALE192_160: h x w = 192 x 160
        '''
        self.df = df
        self.rescale227 = Rescale((227, 227))
        self.rescale192_160 = Rescale((192, 160))
        self.rescale_lower_size = 227
        self.rescale_upper_size = 256
        self.recale_middle_size = (self.rescale_lower_size + self.rescale_upper_size) / 2
        self.random_gamma = RandomGamma((0.4, 1.6))
        self.random_rescale = RandomRescale((self.rescale_lower_size, self.rescale_upper_size))
        self.random_flip = RandomFlip()
        self.random_crop = RandomCrop((227, 227))
        self.middle_of_random_rescale = Rescale((self.recale_middle_size, self.recale_middle_size))
        self.center_crop = CenterCrop((227, 227))
        self.to_tensor = transforms.ToTensor()
        self.normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                              std=[0.229, 0.224, 0.225])
        self.mode = mode

        self.label_name = self.df.iloc[0][:-1].index.tolist()

    def __len__(self):
        return len(self.df)

    def plot_sample(self, i):
        import matplotlib.pyplot as plt
        sample = self[i]
        image = sample['raw_image']
        plt.figure(dpi=72)
        plt.imshow(image)

    def __getitem__(self, i):
        sample = self.df.iloc[i]

        image = io.imread(const.BASE_PATH + 'img_align/' + str(sample.name))
        if self.mode == 'RESCALE227':
            image = self.rescale227(image)
        elif self.mode == 'RESCALE192_160':
            image = self.rescale192_160(image)
        else:
            raise NotImplementedError

        raw_image = image.copy()
        # support special numpy type
        image = image.copy()

        # convert to tensor and normalize
        image = self.to_tensor(image)
        image = self.normalize(image)
        image = image.float()

        label = sample[:-1].values.astype(np.int64)

        ret = {
            'image': image,
            'raw_image': raw_image.astype(np.float32),
            'label': label,
        }
        return ret

